package com.example.jedis.controller;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import java.util.Arrays;
import java.util.UUID;
import java.util.concurrent.TimeUnit;

/**
 * 描述该类
 *
 * @author <a href="920786312@qq.com">周飞</a>
 * @class: RedisTestController
 * @date 2022/2/3 14:11
 * @Verson 1.0 -2022/2/3 14:11
 * @see
 */
@RestController
public class LockTestController {

    private RedisTemplate redisTemplate;

    @Autowired(required = false)
    public void setRedisTemplate(RedisTemplate redisTemplate) {
        //解决一下问题
//        192.168.187.138:6379> keys *
//                  1) "\xac\xed\x00\x05t\x00\x02k1"
//                  2) "\xac\xed\x00\x05t\x00\x03num"
        RedisSerializer stringSerializer = new StringRedisSerializer();
        redisTemplate.setKeySerializer(stringSerializer);
        redisTemplate.setValueSerializer(stringSerializer);
        redisTemplate.setHashKeySerializer(stringSerializer);
        redisTemplate.setHashValueSerializer(stringSerializer);
        this.redisTemplate = redisTemplate;
    }


    @RequestMapping("lockTest")
    public void testLockLua(@RequestParam("proId") String proId) {
        // 声明一个uuid ,保证加锁和解锁必须是同一个客户端
        String uuid = UUID.randomUUID().toString();
        // 当前商品id获取到锁(秒杀商品场景)
        String locKey = "lock:" + proId;
        // 获取锁, 这里的命令是: set locKey uuid NX EX  3
        // 过期时间是为了防止死锁
        Boolean lock = redisTemplate.opsForValue().setIfAbsent(locKey, uuid, 3, TimeUnit.SECONDS);

        if (lock) {
            // -----业务逻辑开始-------
            // 获取缓存中的num 数据
            Object value = redisTemplate.opsForValue().get("num");
            // 如果是空，
            if (StringUtils.isEmpty(value)) {
                value=0;
            }
            // 转int 类型
            int num = Integer.parseInt(value + "");
            // 使num 每次+1 放入缓存
            redisTemplate.opsForValue().set("num", String.valueOf(++num));
            // -----业务逻辑结束-------


            // 使用lua脚本来保证释放锁的原子性
            // 定义lua 脚本
            String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";

            // 使用redis执行lua执行
            DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>();
            redisScript.setScriptText(script);

            // 设置一下返回值类型 为Long
            // 因为删除判断的时候，返回的0,给其封装为数据类型。如果不封装那么默认返回String 类型，
            // 那么返回字符串与0 会有发生错误。
            redisScript.setResultType(Long.class);
            // 第1个参数是script 脚本 ，第2个参数是key，第3个参数是key所对应的值。
            redisTemplate.execute(redisScript, Arrays.asList(locKey), uuid);
        } else {
            // 其他线程等待
            try {
                // 睡眠。
                Thread.sleep(1000);
                // 睡醒了之后，调用方法。
                testLockLua(proId);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}